package com.ee.bruscar.configuration.maker;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

import com.ee.bruscar.configuration.hint.FieldHint;
import com.ee.bruscar.configuration.hint.Hint;
import com.ee.bruscar.configuration.hint.ModelHint;
import com.ee.bruscar.configuration.hint.lookup.HintLookup;
import com.ee.bruscar.exceptions.BruscarException;
import com.ee.bruscar.util.reflection.TypeChecker;

public class HintedModelMaker implements Maker
{
	private ModelHint hint;
	private HintLookup lookup;
	
	public HintedModelMaker(ModelHint modelHint, HintLookup lookup )
	{
		this.hint = modelHint;
		this.lookup = lookup;
	}

	public Object make()
	{
		try
		{
			Class<?> clazzToMake = Class.forName( hint.getHintClass() );
			Object instance = clazzToMake.newInstance();
			
			List<Hint> fieldHints = hint.getFieldHints();
			
			for( Hint hint : fieldHints )
			{
				if( hint.getRef() != null )
				{
					ModelHint modelHint = lookup.getModelHintFor( hint.getRef() );
					Object result = new HintedModelMaker( modelHint, lookup ).make();
					Method setter = clazzToMake.getMethod( TypeChecker.getSetterName( hint.getName() ), result.getClass() );
					setter.invoke(instance, result);
				}
				else if( ((FieldHint) hint).getModelHint() != null )
				{
					ModelHint modelHint = ((FieldHint)hint).getModelHint();
					Object result = new HintedModelMaker( modelHint, lookup ).make();
					Method setter = clazzToMake.getMethod( TypeChecker.getSetterName(hint.getName()), result.getClass());
					setter.invoke(instance, result);
				}
				else
				{
					String fieldName = hint.getName();
					Class<?> setterType = getSetterType( instance.getClass(), fieldName );
					String fieldValue = ((FieldHint) hint).getValue();
					Method setter = clazzToMake.getMethod( TypeChecker.getSetterName( fieldName ), setterType );
					Object value = getHintFromValue( fieldValue, setterType );
					setter.invoke( instance, value );
				}
			}
			
			return instance;
		} 
		catch (Exception e)
		{
			throw new BruscarException( this.getClass().getName() + " make " + hint.getHintClass(), e );
		}

	}

	private Class<?> getSetterType(Class<?> classToCheck, String fieldName)
	{
		try
		{
			Field field = classToCheck.getDeclaredField( fieldName );
			return field.getType();
		} 
		catch (Exception e)
		{
			throw new BruscarException( "can't get field: " + fieldName + " from: " + classToCheck.getName(), e );
		}
	}

	private Object getHintFromValue(String fieldValue, Class<?> expectedType )
	{
		if( expectedType.equals( String.class ))
		{
			String[] options = fieldValue.split(",");
			int index = getRandomIndex( options.length );
			return options[index];
		}
		else if( expectedType.equals(Boolean.TYPE))
		{
			return fieldValue.equals("true") ? true : false;
		}
		//TODO return default makers for complex objects
		//TODO support other simple data types: Date,int,long,double,etc
		//return null;
		throw new BruscarException("Unsupported field type: " + expectedType.getSimpleName() );
	}
	
	public int getRandomIndex(int length)
	{
		double random = Math.random();
		double multiplied = random * length;
		
		Double dbl = new Double( multiplied );
		return dbl.intValue();
	}
	

}
